package main;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Iterator;

import org.antlr.runtime.RecognitionException;

import graph.*;

import com.martiansoftware.jsap.FlaggedOption;
import com.martiansoftware.jsap.JSAP;
import com.martiansoftware.jsap.JSAPException;
import com.martiansoftware.jsap.JSAPResult;

import compiler.PropertiesCompiler;
import compiler.SourceCompiler;

import solver.Decision;
import solver.Solver;
import transform.Transformer;
import utils.MergeGraph;

/**
 * This is the main class
 * 
 * 
 */
public class Application {

	public static final String[] DEFAULT_TRANSFORMATIONS = { "ConstantPropagationTransformer" };
	public static final String DEFAULT_SOLVER = "TrueSolver";
	
	public static String sourceFile = null;
	public static String propertiesFile = null;
	public static String[] trfNames = null;
	public static String solverName = null;

	public static void main(String args[]) {
		/*
		 * 1. Read from (parse) the command line: the source file, the
		 * properties file and the configuration
		 * 
		 * Format of the Command Line: 
		 * (-f|--source) <source> (-p|--prop) <properties> 
		 * 		[(-t|--transform) transformers1:transformers2:...:transformersN ] 
		 * 		[(-s|--solver) <solver>]
		 */
		parseCommandLine(args);
		
		Transformer[] trfObjs = null;	//List of transformers objects used
		Solver solverObj = null;		//The solver object used
		Graph[] graphs;	//An array of transformed graphs
		
		/*
		 * 2. Compile the source and the properties files and merge their parse
		 * trees
		 */
		SourceCompiler sCompiler = new SourceCompiler();
		PropertiesCompiler pCompiler = new PropertiesCompiler();
		
		Graph graph = null;
		Graph prop = null;
		Graph mergedGraph = null;
		try {
			graph = sCompiler.getGraph(sourceFile);
			prop = pCompiler.getGraph(propertiesFile);
			
			MergeGraph merger = new MergeGraph(graph,prop);
			mergedGraph = merger.merge();
		} catch (IOException e1) {
			System.err.println("Error reading file");
			e1.printStackTrace();
		} catch (RecognitionException e1) {
			System.err.println("Compilation error");
			e1.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}


		/*
		 * 3. To support a pluggable architecture, we need to load transformer and solver
		 * classes at runtime
		 */
		
		try {
			trfObjs = new Transformer[trfNames.length];
			for (int i = 0; i < trfObjs.length; i++) {

				trfObjs[i] = (Transformer) Class.forName(
						"transform." + trfNames[i]).newInstance();

			}
			solverObj = (Solver) Class.forName(
					"solver." + solverName).newInstance();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		/*
		 * Input the graph through a series of transformations
		 * If after one of the transformation, the property node had a value,
		 * the program will end outputting the decision.
		 */
		graphs = new Graph[trfObjs.length+1];
		graphs[0] = mergedGraph;
		boolean trfOK = true;
		for(int i=0; i<trfObjs.length; i++)
		{
			trfOK = trfObjs[i].setGraph(graphs[i]);
			trfOK = trfObjs[i].runTransform();
			Graph newGraph =  trfObjs[i].getGraph();
			graphs[i+1] = newGraph;
			
			if(!trfOK) System.err.println("Error in transformation:" + trfNames[i]);
			if(newGraph.isResolved()!=Decision.UNKNOWN)
			{
				System.out.println("Result: " + newGraph.isResolved());
				return;
			}
		}

		PrintWriter writer = null;
		try {
			writer = new PrintWriter("graph.dot");
			writer.print(Node.toDotLanguage(graphs[1].getRoot()));
			writer.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		
		
		/*
		 * 4. After the last transformation, input the graph through the solver
		 * and obtain result
		 * For now we will not use a solver. But instead if there were no decision,
		 * it will output UNKNOWN.
		 */
		System.out.println("Result: " + Decision.UNKNOWN);

	}
	
	/**
	 * Parse command line and save the result into the member variables: sourceFile,
	 * propertiesFile and trfNames.
	 * If there was a parsing error, it prints an error message detailing where the input
	 * was malformed, and exits the program
	 * 
	 * Experimentally, we are using the JSAP (Java Simple argument Parser) library to parse
	 * the input. We may think of moving to another library (eg. Apache Commons CLI)
	 * 
	 * @param args The command line argument to parse
	 */
	private static void parseCommandLine(String[] args)
	{
		try {
			
			/*
			 * Create the parser and add to it the three options to parse: s, p & t
			 */
			JSAP clParser = new JSAP();
			FlaggedOption sourceOpt = new FlaggedOption("source")
					.setStringParser(JSAP.STRING_PARSER)
					.setRequired(true)
					.setShortFlag('f')
					.setLongFlag("source");
			sourceOpt.setHelp("The source file to analyze");

			clParser.registerParameter(sourceOpt);

			FlaggedOption propertiesOpt = new FlaggedOption("properties")
					.setStringParser(JSAP.STRING_PARSER)
					.setRequired(true)
					.setShortFlag('p')
					.setLongFlag("prop");
			propertiesOpt.setHelp("The properties file");

			clParser.registerParameter(propertiesOpt);

			FlaggedOption transformOpt = new FlaggedOption("transformers")
					.setRequired(JSAP.NOT_REQUIRED)
					.setList(true)
					.setListSeparator(':')
					.setShortFlag('t')
					.setLongFlag("transform");
			transformOpt.setHelp("The list of transformations to apply");

			clParser.registerParameter(transformOpt);
			
			FlaggedOption solverOpt = new FlaggedOption("solver")
			.setRequired(JSAP.NOT_REQUIRED)
			.setShortFlag('s')
			.setLongFlag("solver");
	transformOpt.setHelp("The solver to apply");

	clParser.registerParameter(solverOpt);

			/*
			 * Parse the arguments with the created parser
			 */
			JSAPResult result = clParser.parse(args);

			/*
			 * If parsing was successful, extract each token and save it in its respective
			 * variable.
			 * else, print an error message describing where the error occurred and exit.
			 */
			if (result.success()) {
				sourceFile = result.getString("source");
				propertiesFile = result.getString("properties");
				
				if(result.contains("transformers"))					
					trfNames = result.getStringArray("transformers");
				else
					trfNames = DEFAULT_TRANSFORMATIONS;
				
				if(result.contains("solver"))
					solverName = result.getString("solver");
				else
					solverName = DEFAULT_SOLVER;
				
			} else {
				System.err.println(clParser.getUsage());
				System.err.println();

				Iterator errs = result.getErrorMessageIterator();
				while (errs.hasNext()) {
					System.err.println("Error: " + errs.next());
				}
				System.exit(0);
			}
			
		} catch (JSAPException e) {
			e.printStackTrace();
		}
	}

}
